package matrix;

import java.util.Arrays;

import utils.Utils;

public class Matrix {
	 
	double[][] matrix;
	int cols;
	int rows;
	
	Matrix L;
	Matrix U;
	
	public Matrix(double[][] matrix){
		this.matrix = matrix;
		cols = matrix[0].length;
		rows = matrix.length;
	}
	
	public double[][] getMatrix() {
		return matrix;
	}
	
	public int getCols() {
		return cols;
	}
	
	public int getRows() {
		return rows;
	}
	
	public static Matrix zero(int rows, int cols){
		double[][] matrix = new double[rows][cols];
		for(int i=0;i<rows;i++){
			Arrays.fill(matrix[i], 0.0);
		}
		return new Matrix(matrix);
	}
	
	public Matrix add(Matrix b){
		if(rows != b.getRows() || cols != b.getCols()){
			throw new IllegalArgumentException();
		}
		
		double[][] temp = new double[rows][cols];
		for(int row=0;row<rows;row++){
			for(int col=0;col<cols;col++){
				temp[row][col] = matrix[row][col] + b.getMatrix()[row][col];
			}
		}
		
		return new Matrix(temp);
	}
	
	public Matrix sub(Matrix b) {
		if(rows != b.getRows() || cols != b.getCols()){
			throw new IllegalArgumentException();
		}
		
		double[][] temp = new double[rows][cols];
		for(int row=0;row<rows;row++){
			for(int col=0;col<cols;col++){
				temp[row][col] = matrix[row][col] - b.getMatrix()[row][col];
			}
		}
		
		return new Matrix(temp);
	}
	
	public Matrix mult(Matrix b) {
		if(cols != b.getRows()){
			throw new IllegalArgumentException();
		}
		
		double[][] temp = new double[rows][b.getCols()];
		for(int i=0;i<temp.length;i++){
			for(int j=0;j<temp[0].length;j++){
				temp[i][j] = dotProduct(getRow(i), b.getCol(j)); 
			}
		}
		
		return new Matrix(temp);
	}
	
	public Matrix transpose() {
		double[][] trans = new double[cols][rows];
		for(int row=0;row<rows;row++){
			for(int col=0;col<cols;col++){
				trans[col][row] = matrix[row][col];
			}
		}
		return new Matrix(trans);
	}
	
	public double trace(){
		if(rows != cols){
			throw new IllegalArgumentException();
		}
		double trace = 0;
		for(int i=0;i<cols;i++){
			trace += matrix[i][i];
		}
		
		return trace;
	}
	
	public double[] getRow(int row){
		if(row >= rows || row < 0){
			throw new IllegalArgumentException();
		}
		return matrix[row];
	}
	
	public Matrix scale(double scalar){
		double[][] temp = new double[matrix.length][];
		for(int i=0;i<rows;i++){
			temp[i] = Utils.scaleVector(matrix[i], scalar);
		}
		return new Matrix(temp);
	}
	
	public double[] getCol(int col){
		if(col >= cols || col < 0){
			throw new IllegalArgumentException();
		}
		Matrix temp = new Matrix(matrix).transpose();
		return temp.getRow(col);
	}
	
	public static double dotProduct(double[] row, double[] col){
		if( row == null || col == null || (row.length != col.length)){
			throw new IllegalArgumentException();
		}
		double result = 0;
		for(int i=0;i<row.length;i++){
			result += (row[i]*col[i]);
		}
		return result;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Matrix other = (Matrix) obj;
		if (cols != other.cols)
			return false;
		if (rows != other.rows)
			return false;
		if (!Arrays.deepEquals(matrix, other.matrix))
			return false;
		
		return true;
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + cols;
		result = prime * result + Arrays.hashCode(matrix);
		result = prime * result + rows;
		return result;
	}

	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		for(int row=0;row<rows;row++){
			sb.append(Arrays.toString(matrix[row])+"\n");
		}
		
		return sb.toString();
	}

	public static Matrix identity(int n) {
		if(n <= 0){
			throw new IllegalArgumentException();
		}
		double[][] matrix = new double[n][n];
		for(int i=0;i<n;i++){
			matrix[i][i] = 1.0;
		}
		return new Matrix(matrix);
	}


	public Matrix setElement(double val, int i, int j) {
		if(i >= matrix.length || i < 0){
			throw new IllegalArgumentException();
		}
		
		if(j >= matrix[0].length || j < 0){
			throw new IllegalArgumentException();
		}
		
		matrix[i][j] = val;
		return new Matrix(matrix);
	}

	public Matrix augment(double[] b) {
		if(b.length != rows){
			throw new IllegalArgumentException();
		}
		double[][] temp = Utils.addColumn(matrix, b);
		return new Matrix(temp);
	}

	public Matrix gaussian() throws NonRegularMatrixException {
		// cols-1 since this matrix is augmented
		for(int col = 0; col<cols-1;col++){
			if(matrix[col][col] == 0){
				throw new NonRegularMatrixException();
			} 
			
			for(int row = col+1;row<rows;row++){
				double scalar = matrix[row][col]/matrix[col][col];
				matrix[row] = Utils.ero1(matrix, -scalar, col, row);
			}
		}
		return new Matrix(matrix);
	}

	public Matrix getCoefficientMatrix() {
		Matrix temp = transpose();
		double[][] rtn = temp.getMatrix();
		int rtnCols = temp.getRows();
		rtn = Utils.dropColumn(rtn, rtnCols);
		return new Matrix(rtn).transpose();
	}
	
	public void LUDecomp() throws NonRegularMatrixException{
		
		double[][] copy = Utils.arrayCopy2D(matrix);
		
		double[][] lMatrix = identity(rows).getMatrix();
		
		for(int col = 0; col<cols-1;col++){
			if(copy[col][col] == 0){
				throw new NonRegularMatrixException();
			} 
			
			for(int row = col+1;row<rows;row++){
				double scalar = copy[row][col]/copy[col][col];
				lMatrix[row][col] = scalar;
				copy[row] = Utils.ero1(copy, -scalar, col, row);
			}
		}
		U = new Matrix(copy);
		L = new Matrix(lMatrix);
	}
	
	public Matrix getU() {
		return U;
	}
	
	public Matrix getL() {
		return L;
	}
	
	

}
